﻿//------------------------------------------------------------------------------
//  Namespace: FruitVentDesign.Controls
//  
//  Function： N/A
//  Name： Table
//  
//  Ver       Time                     Author
//  0.10      2021/6/26 18:42:25      FruitVent
//
//  此代码版权归作者本人FruitVent所有
//  源代码使用协议遵循本仓库的开源协议及附加协议，若本仓库没有设置，则按MIT开源协议授权
//  CSDN博客：https://blog.csdn.net/weixin_39552347
//  源代码仓库：https://gitee.com/fruitvent
//  感谢您的下载和使用
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
using FruitVentDesign.Commands;
using FruitVentDesign.Models;
using FruitVentDesign.Utils;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Reflection;
using System.Timers;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Media;

namespace FruitVentDesign.Controls
{
    public class Table : DataGrid
    {
        #region attached dependency property
        /// <summary>
        /// whether to page
        /// </summary>
        public static readonly DependencyProperty IsDragRowProperty = DependencyProperty.Register(
            "IsDragRow",
            typeof(bool),
            typeof(Table),
            new PropertyMetadata(false)
        );

        public bool IsDragRow
        {
            get { return (bool)GetValue(IsDragRowProperty); }
            set { SetValue(IsDragRowProperty, value); }
        }

        /// <summary>
        /// drag row display path
        /// </summary>
        public static readonly DependencyProperty DragRowDisplayPathProperty = DependencyProperty.Register(
            "DragRowDisplayPath",
            typeof(string),
            typeof(Table),
            new PropertyMetadata(null)
        );

        public string DragRowDisplayPath
        {
            get { return (string)GetValue(DragRowDisplayPathProperty); }
            set { SetValue(DragRowDisplayPathProperty, value); }
        }

        /// <summary>
        /// selected path
        /// </summary>
        public static readonly DependencyProperty SelectedPathProperty = DependencyProperty.Register(
            "SelectedPath",
            typeof(string),
            typeof(Table),
            new PropertyMetadata(null)
        );

        public string SelectedPath
        {
            get { return (string)GetValue(SelectedPathProperty); }
            set { SetValue(SelectedPathProperty, value); }
        }

        /// <summary>
        /// sort path
        /// </summary>
        public static readonly DependencyProperty SortPathProperty = DependencyProperty.Register(
            "SortPath",
            typeof(string),
            typeof(Table),
            new PropertyMetadata("Sort")
        );

        public string SortPath
        {
            get { return (string)GetValue(SortPathProperty); }
            set { SetValue(SortPathProperty, value); }
        }

        /// <summary>
        /// whether to page
        /// </summary>
        public static readonly DependencyProperty IsPaginationProperty = DependencyProperty.Register(
            "IsPagination",
            typeof(bool),
            typeof(Table),
            new PropertyMetadata(true)
        );

        public bool IsPagination
        {
            get { return (bool)GetValue(IsPaginationProperty); }
            set { SetValue(IsPaginationProperty, value); }
        }

        /// <summary>
        /// filter member path
        /// </summary>
        public static readonly DependencyProperty FilterMemberPathProperty = DependencyProperty.RegisterAttached(
            "FilterMemberPath",
            typeof(string),
            typeof(Table)
            );

        public static void SetFilterMemberPath(DependencyObject o, string memberPath)
        {
            o.SetValue(FilterMemberPathProperty, memberPath);
        }

        public string GetFilterMemberPath(DependencyObject o)
        {
            return (string)o.GetValue(FilterMemberPathProperty);
        }

        /// <summary>
        /// items used to do filter
        /// </summary>
        public static readonly DependencyProperty FilterItemsProperty = DependencyProperty.RegisterAttached(
            "FilterItems",
            typeof(IEnumerable<FilterItem>),
            typeof(Table)
            );

        public static void SetFilterItems(DependencyObject o, IEnumerable<FilterItem> filterItems)
        {
            o.SetValue(FilterItemsProperty, filterItems);
        }

        public static IEnumerable<FilterItem> GetFilterItems(DependencyObject o)
        {
            return (IEnumerable<FilterItem>)o.GetValue(FilterItemsProperty);
        }

        #endregion

        #region dependency property
        /// <summary>
        /// items source
        /// </summary>
        public new static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register(
            "ItemsSource",
            typeof(IEnumerable<object>),
            typeof(Table),
            new PropertyMetadata(null, ItemsSourcePropertyChanged)
        );

        private static void ItemsSourcePropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
        {
            // Trace.WriteLine("items source property changed");
            if (o is Table grid)
            {
                // remove listener
                if (e.OldValue is INotifyCollectionChanged oldSource)
                {
                    oldSource.CollectionChanged -= grid.ItemsSource_CollectionChanged;
                }
                // add listener
                if (e.NewValue is INotifyCollectionChanged newSource)
                {
                    newSource.CollectionChanged += grid.ItemsSource_CollectionChanged;
                }
                // notify source changed
                grid.ItemsSource_Changed();
            }
        }

        public new IEnumerable<object> ItemsSource
        {
            get { return (IEnumerable<object>)GetValue(ItemsSourceProperty); }
            set { SetValue(ItemsSourceProperty, value); }
        }

        /// <summary>
        /// page size
        /// </summary>
        public static readonly DependencyProperty PageSizeProperty = DependencyProperty.Register(
            "PageSize",
            typeof(int),
            typeof(Table),
            new PropertyMetadata(10, PageSizePropertyChanged)
            );

        private static void PageSizePropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
        {
            // Trace.WriteLine("page size property changed");
            if (o is Table grid)
            {
                grid.PageSizeChanged();
            }
        }

        public int PageSize
        {
            get { return (int)GetValue(PageSizeProperty); }
            set { SetValue(PageSizeProperty, value); }
        }

        /// <summary>
        /// total pages
        /// </summary>
        public static readonly DependencyProperty PageNumProperty = DependencyProperty.Register(
            "PageNum",
            typeof(int),
            typeof(Table),
            new PropertyMetadata(0)
        );

        public int PageNum
        {
            get { return (int)GetValue(PageNumProperty); }
            set { SetValue(PageNumProperty, value); }
        }

        /// <summary>
        /// current page 1-based
        /// </summary>
        public static readonly DependencyProperty PageCurrentProperty = DependencyProperty.Register(
            "PageCurrent",
            typeof(int),
            typeof(Table),
            new PropertyMetadata(1, PageCurrentPropertyChanged)
        );

        private static void PageCurrentPropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
        {
            // Trace.WriteLine("page current property changed");
            if (o is Table grid)
            {
                grid.PageCurrentChanged();
            }
        }

        public int PageCurrent
        {
            get { return (int)GetValue(PageCurrentProperty); }
            set { SetValue(PageCurrentProperty, value); }
        }


        /// <summary>
        /// PageSizeOptions
        /// </summary>
        public static readonly DependencyProperty PageSizeOptionsProperty = DependencyProperty.Register(
            "PageSizeOptions",
            typeof(int[]),
            typeof(Table),
            new PropertyMetadata(new int[] { 10, 20, 30, 50, 100 })
            );

        public int[] PageSizeOptions
        {
            get { return (int[])GetValue(PageSizeOptionsProperty); }
            set { SetValue(PageSizeOptionsProperty, value); }
        }
        #endregion

        public Table()
        {
            // Columns.CollectionChanged += Columns_CollectionChanged;
            MouseDown += Table_MouseDown;
            //Loaded += Table_Loaded;

            PreviewMouseLeftButtonDown += Table_PreviewMouseLeftButtonDown;
            base.DragOver += Table_DragOver;
            base.DragLeave += Table_DragLeave;
            base.Drop += Table_Drop;
        }

        #region drag sort
        private int prevRowIndex = -1;
        private object _selectItem = null;
        private Popup dragView = null;
        private bool _hasOperatingArea = false;
        private double _operatingAreaWidth = 0D;

        /// <summary>
        /// preview mouse left button down
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Table_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            if (!IsDragRow) return;

            prevRowIndex = UITools.GetDataGridItemCurrentRowIndex(e.GetPosition, this);

            // If the area is selected in the first column, click the selection box, it is not performed.
            if (e.GetPosition(this).X < 30) return;

            // If you allow paging and select the area in a paging operation area, it is not performed.
            if (IsPagination && (e.GetPosition(this).Y > (this.ActualHeight - 40))) return;

            if (_hasOperatingArea && (e.GetPosition(this).X > (this.ActualWidth - _operatingAreaWidth))) return;

            if (prevRowIndex < 0) return;
            SelectedIndex = prevRowIndex;

            var selectedEmp = _selectItem = Items[prevRowIndex];

            if (selectedEmp == null) return;

            string diplay = "";
            if (!string.IsNullOrEmpty(DragRowDisplayPath) && _selectItem != null)
            {
                diplay = ReflectionUtil.GetModelValue(DragRowDisplayPath, _selectItem);
            }

            TextBlock textBlock = new TextBlock()
            {
                Margin = new Thickness(8, 0, 0, 0),
                VerticalAlignment = VerticalAlignment.Center,
                FontSize = 14D,
                FontWeight = FontWeight.FromOpenTypeWeight(2),
                Text = diplay
            };

            StackPanel stackPanel = new StackPanel()
            {
                Margin = new Thickness(4, 3, 8, 3),
                Orientation = Orientation.Horizontal
            };

            stackPanel.Children.Add(textBlock);

            Border border = new Border()
            {
                Background = new SolidColorBrush(Colors.White),
                BorderBrush = new SolidColorBrush(Colors.LightSteelBlue),
                BorderThickness = new Thickness(2),
                Opacity = 0.95D,
                Child = stackPanel
            };

            dragView = new Popup()
            {
                AllowsTransparency = true,
                IsHitTestVisible = false,
                Placement = PlacementMode.RelativePoint,
                Child = border
            };

            DragDropEffects dragdropeffects = DragDropEffects.Move;
            if (DragDrop.DoDragDrop(this, selectedEmp, dragdropeffects)
                                != DragDropEffects.None)
            {
                SelectedItem = selectedEmp;
            }
        }

        /// <summary>
        /// drag over
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Table_DragOver(object sender, DragEventArgs e)
        {
            if (!IsDragRow) return;

            int index = UITools.GetDataGridItemCurrentRowIndex(e.GetPosition, this);

            for (int i = 0; i < Items.Count; i++)
            {
                DataGridRow r = ItemContainerGenerator.ContainerFromIndex(i) as DataGridRow;

                if (r == null) continue;

                if (i != index)
                {
                    r.BorderBrush = new SolidColorBrush(Colors.Transparent);
                    r.BorderThickness = new Thickness(0, 0, 0, 0);
                }
                else
                {
                    r.BorderBrush = new SolidColorBrush(Color.FromRgb(32, 164, 230));
                    Thickness th = new Thickness(0, 0, 0, 0);

                    if (index > prevRowIndex) th = new Thickness(0, 0, 0, 1);
                    else if (index < prevRowIndex) th = new Thickness(0, 1, 0, 0);
                    r.BorderThickness = th;
                }
            }

            if (dragView != null && !dragView.IsOpen)
            {
                dragView.IsOpen = true;
            }

            Size popupSize = new Size(dragView.ActualWidth + 110, dragView.ActualHeight + 10);
            Point p = e.GetPosition(this);
            p.X += 10;
            p.Y += 10;
            dragView.PlacementRectangle = new Rect(p, popupSize);
        }

        /// <summary>
        /// drag leave
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Table_DragLeave(object sender, DragEventArgs e)
        {
            if (!IsDragRow) return;

            if (dragView != null)
            {
                dragView.IsOpen = false;
            }
        }

        /// <summary>
        /// drop
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Table_Drop(object sender, DragEventArgs e)
        {
            if (!IsDragRow) return;

            if (dragView != null)
            {
                dragView.IsOpen = false;
            }

            int index = UITools.GetDataGridItemCurrentRowIndex(e.GetPosition, this);

            if (index < 0 || index == prevRowIndex) return;
            (ItemContainerGenerator.ContainerFromIndex(index) as DataGridRow).BorderThickness = new Thickness(0, 0, 0, 0);

            if (index >= Items.Count - 1)
            {
                index = Items.Count - 1;
            }

            ApplySort(prevRowIndex, index);
            prevRowIndex = -1;

            RePagination();

            OnGetFilterItemsSoure();
        }

        /// <summary>
        /// apply sort
        /// </summary>
        /// <param name="index"></param>
        private void ApplySort(int prevRowIndex, int index)
        {
            int pageCurrent = PageCurrent - 1;
            int pageSize = PageSize;

            if (IsPagination)
            {
                _filteredItems.RemoveAt((pageSize * pageCurrent) + prevRowIndex);
                _filteredItems.Insert((pageSize * pageCurrent) + index, _selectItem);
            }
            else
            {
                _filteredItems.RemoveAt(prevRowIndex);
                _filteredItems.Insert(index, _selectItem);
            }

            for (int i = 0; i < _filteredItems.Count; i++)
            {
                ReflectionUtil.SetModelValue(SortPath, $"{i + 1}", _filteredItems[i]);
            }
        }

        /// <summary>
        /// delete rows
        /// </summary>
        public void DeleteRows()
        {
            if (SelectedPath == null) return;

            int pageCurrent = PageCurrent - 1;
            int pageSize = PageSize;

            for (int i = 0; i < _filteredItems.Count; i++)
            {
                if (System.Convert.ToBoolean(ReflectionUtil.GetModelValue(SelectedPath, _filteredItems[i])))
                {
                    _filteredItems.RemoveAt((pageSize * pageCurrent) + i);
                    i--;
                }
            }

            for (int i = 0; i < _filteredItems.Count; i++)
            {
                ReflectionUtil.SetModelValue(SortPath, $"{i + 1}", _filteredItems[i]);
            }

            UpdatePaginationInfo();

            RePagination();

            OnGetFilterItemsSoure();
        }

        #endregion

        #region basic definitions
        //public int[] PageSizeOptions { get; set; } = new int[] { 10, 20, 30, 50, 100 };

        private void Table_MouseDown(object sender, MouseButtonEventArgs e)
        {
            this.Focus();
        }

        //private void Table_Loaded(object sender, RoutedEventArgs e)
        //{
        //    HandleColumns(Columns);
        //}

        //private void HandleColumns(System.Collections.IList columns)
        //{
        //    if (columns == null) { return; }

        //    foreach (DataGridColumn column in columns)
        //    {
        //        string filterMemberPath = GetFilterMemberPath(column);
        //        var filterItems = GetFilterItems(column);

        //        if (filterMemberPath != null && filterItems != null)
        //        {
        //            if (column.HeaderTemplate == null)
        //            {
        //                column.HeaderTemplate = new DataTemplate
        //                {
        //                    VisualTree = CreateColHeader(column.Header, column.HeaderTemplate, filterItems)
        //                };
        //            }
        //        }

        //        string headerTitle = Convert.ToString(column.Header);

        //        if (column != null && headerTitle == "操作")
        //        {
        //            _hasOperatingArea = true;
        //            _operatingAreaWidth = column.ActualWidth;
        //        }
        //    }
        //}

        ///// <summary>
        ///// 
        ///// </summary>
        ///// <param name="title"></param>
        ///// <param name="titleTemplate"></param>
        ///// <param name="filterItems"></param>
        ///// <returns></returns>
        //private FrameworkElementFactory CreateColHeader(object title, DataTemplate titleTemplate, IEnumerable<FilterItem> filterItems)
        //{
        //    var stackPanelFactory = new FrameworkElementFactory(typeof(StackPanel));

        //    var filterHeaderFactory = new FrameworkElementFactory(typeof(FilterHeader));
        //    filterHeaderFactory.SetValue(FilterHeader.TitleProperty, title);
        //    filterHeaderFactory.SetValue(FilterHeader.TitleTemplateProperty, titleTemplate);
        //    filterHeaderFactory.SetValue(FilterHeader.FilterItemsProperty, filterItems);
        //    stackPanelFactory.AppendChild(filterHeaderFactory);

        //    return stackPanelFactory;

        //}

        private readonly Timer timer = new Timer();

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
            timer.Elapsed += new ElapsedEventHandler(PollingEvent);
            timer.Interval = 1;
            timer.Start();
        }

        private void PollingEvent(object sender, ElapsedEventArgs e)
        {
            _ = Application.Current?.Dispatcher.BeginInvoke(new Action(() =>
            {
                HandleColumns(Columns);
            }));
        }

        private void HandleColumns(System.Collections.IList columns)
        {
            if (columns == null) { return; }
            foreach (DataGridColumn column in columns)
            {
                DataGridColumnHeader headerObj = GetColumnHeaderFromColumn(column, this);
                if (headerObj != null)
                {
                    string filterMemberPath = GetFilterMemberPath(column);
                    IEnumerable<FilterItem> filterItems = GetFilterItems(column);
                    FilterHeader filterItem = UITools.FindFirstChild<FilterHeader>(headerObj);
                    if (filterItem != null && filterMemberPath != null && filterItems != null)
                    {
                        filterItem.Visibility = Visibility.Visible;
                        filterItem.FilterItems = filterItems;
                    }
                    timer.Stop();
                }
                else
                {
                    break;
                }
            }
        }

        private DataGridColumnHeader GetColumnHeaderFromColumn(DataGridColumn column, DataGrid dataGrid)
        {
            // dataGrid is the name of your DataGrid. In this case Name="dataGrid"
            List<DataGridColumnHeader> columnHeaders = GetVisualChildCollection<DataGridColumnHeader>(dataGrid);
            foreach (DataGridColumnHeader columnHeader in columnHeaders)
            {
                if (columnHeader.Column == column)
                {
                    return columnHeader;
                }
            }
            return null;
        }

        public List<T> GetVisualChildCollection<T>(object parent) where T : Visual
        {
            List<T> visualCollection = new List<T>();
            GetVisualChildCollection(parent as DependencyObject, visualCollection);
            return visualCollection;
        }

        private void GetVisualChildCollection<T>(DependencyObject parent, List<T> visualCollection) where T : Visual
        {
            int count = VisualTreeHelper.GetChildrenCount(parent);
            for (int i = 0; i < count; i++)
            {
                DependencyObject child = VisualTreeHelper.GetChild(parent, i);
                if (child is T)
                {
                    visualCollection.Add(child as T);
                }
                else if (child != null)
                {
                    GetVisualChildCollection(child, visualCollection);
                }
            }
        }
        #endregion

        #region operations that will refresh view
        //internal void FilterPopup_Closed(object sender, EventArgs e)
        //{
        //	// update header status
        //	if (sender is Popup popup)
        //	{
        //		if (popup.Tag is FilterHeader header)
        //		{
        //			header.UpdateHasSelectedFilterItems();
        //		}
        //	}

        //	ReFilterAndPagination();
        //}

        public void DoFilter()
        {
            ReFilterAndPagination();
        }

        /// <summary>
        /// if items source is INotifyCollectionChanged, this listener will be used
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ItemsSource_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            ItemsSource_Changed();
        }

        private void ItemsSource_Changed()
        {
            ReFilterAndPagination();
        }

        /// <summary>
        /// when page size changed:
        ///  a. update pagination infomation
        ///  b. re-pagination filtered items
        /// </summary>
        private void PageSizeChanged()
        {
            UpdatePaginationInfo();
            RePagination();
        }

        /// <summary>
        /// when page current changed:
        ///  a. re-pagination filtered items
        /// </summary>
        private void PageCurrentChanged()
        {
            if (PageNum > 0 && PageCurrent > PageNum)
            {
                PageCurrent = PageNum;
            }

            RePagination();
        }

        #region command for move to pre-page
        private ICommand _moveToPrePageCommand;
        public ICommand MoveToPrevPageCommand
        {
            get
            {
                if (_moveToPrePageCommand == null)
                {
                    _moveToPrePageCommand = new SimpleCommand(MoveToPrevPage, CanMoveToPrevPage);
                }
                return _moveToPrePageCommand;
            }
        }

        private void MoveToPrevPage()
        {
            int pageCurrent = PageCurrent;
            if (pageCurrent > 1)
            {
                // this will refresh current view
                PageCurrent = pageCurrent - 1;
            }
        }

        private bool CanMoveToPrevPage()
        {
            return PageCurrent > 1;
        }

        #endregion

        #region command for move to next page
        private ICommand _moveToNexPageCommand;
        public ICommand MoveToNextPageCommand
        {
            get
            {
                if (_moveToNexPageCommand == null)
                {
                    _moveToNexPageCommand = new SimpleCommand(MoveToNextPage, CanMoveToNextPage);
                }
                return _moveToNexPageCommand;
            }
        }

        private void MoveToNextPage()
        {
            int pageCurrent = PageCurrent;
            if (pageCurrent < PageNum)
            {
                // this will refresh current view
                PageCurrent = pageCurrent + 1;
            }
        }

        private bool CanMoveToNextPage()
        {
            return PageCurrent < PageNum;
        }

        #endregion

        private bool _suppressRefresh = false;
        private List<object> _filteredItems = null;

        /// <summary>
        /// get display source
        /// </summary>
        /// <param name="items"></param>
        /// <returns></returns>
        private IEnumerable<object> GetViewSource(IEnumerable<object> items)
        {
            if (items == null) return null;

            List<object> views = new List<object>();
            foreach (var item in items)
            {
                views.Add(item);
            }

            return views;
        }

        /// <summary>
        /// do filter and paginiation again
        /// </summary>
        private void ReFilterAndPagination()
        {
            if (_suppressRefresh) { return; }

            _filteredItems = ApplyFilter(ItemsSource);

            OnGetFilterItemsSoure();

            // filtered items chagned, update pagination information
            UpdatePaginationInfo();

            var pagedItems = GetViewSource(_filteredItems);
            if (IsPagination)
            {
                pagedItems = ApplyPagination(_filteredItems);
            }
            base.ItemsSource = pagedItems;
        }

        /// <summary>
        /// do pagination again
        /// </summary>
        private void RePagination()
        {
            if (_suppressRefresh) { return; }

            var pagedItems = GetViewSource(_filteredItems);
            if (IsPagination)
            {
                pagedItems = ApplyPagination(_filteredItems);
            }
            base.ItemsSource = pagedItems;

        }

        #endregion

        #region pagination
        /// <summary>
        /// update information for pagination, this will not refresh view
        /// </summary>
        private void UpdatePaginationInfo()
        {
            _suppressRefresh = true;

            int pageNum;

            int pageSize = PageSize;
            if (pageSize <= 0) { pageNum = 0; }
            else
            {
                int itemsCount = _filteredItems == null ? 0 : _filteredItems.Count;
                pageNum = itemsCount / pageSize;
                if (itemsCount % pageSize != 0) { pageNum += 1; }
            }
            // update page-num
            PageNum = pageNum;
            // adjust page-current
            if (pageNum <= 0)
            {
                PageCurrent = 1;
            }
            else if (PageCurrent > pageNum)
            {
                PageCurrent = pageNum;
            }

            _suppressRefresh = false;
        }

        private IEnumerable<object> ApplyPagination(IEnumerable<object> items)
        {
            int pageCurrent = PageCurrent - 1;
            if (pageCurrent < 0) { return null; } // faild to pagination
            int pageSize = PageSize;
            if (pageSize <= 0) { return null; } // faild to paginatin

            // pageCurrent>=0 && pageSize>=1

            int currentPageStart = pageCurrent * pageSize;
            int currentPageEnd = currentPageStart + pageSize - 1;

            List<object> pageItems = new List<object>();
            if (items == null) { return null; }
            int i = 0;
            foreach (var item in items)
            {
                if (currentPageStart <= i && i <= currentPageEnd)
                {
                    pageItems.Add(item);
                }

                i += 1;
            }

            return pageItems;
        }
        #endregion

        #region filter items source
        /// <summary>
        /// filter speicifed items
        /// </summary>
        /// <param name="items"></param>
        /// <returns></returns>
        private List<object> ApplyFilter(IEnumerable<object> items)
        {
            if (items == null) { return null; }

            List<object> filteredItems = new List<object>();
            foreach (var item in items)
            {
                bool passFilter = true;
                foreach (var col in Columns)
                {
                    if (!IsSatisfied(item, col))
                    {
                        passFilter = false;
                        break;
                    }
                }

                if (passFilter) { filteredItems.Add(item); }
            }

            return filteredItems;
        }

        private bool IsSatisfied(object item, DataGridColumn column)
        {
            string filterMemberPath = GetFilterMemberPath(column);
            if (filterMemberPath == null) { return true; } // no filter items, no filtering

            IEnumerable<FilterItem> filterItems = GetFilterItems(column);
            if (filterItems == null) { return true; } // no filter items, no filtering

            object value = GetMemberValue(item, filterMemberPath);

            List<FilterItem> selectedFilterItems = new List<FilterItem>();
            foreach (var filterItem in filterItems)
            {
                if (filterItem.IsSelected) { selectedFilterItems.Add(filterItem); }
            }
            if (selectedFilterItems.Count == 0) { return true; } // no selected filter items no filtering

            foreach (var filterItem in selectedFilterItems)
            {
                if (filterItem.IsPassed(filterItem.DisplayText, value))
                {
                    return true;
                }
            }

            return false;
        }

        /// <summary>
        /// get string value of a property by reflection
        /// </summary>
        /// <param name="obj"></param>
        /// <param name="memberPath"></param>
        /// <returns></returns>
        private object GetMemberValue(object obj, string memberPath)
        {
            string[] propNameSubs = memberPath.Split(new char[] { '.' });

            object current = obj;
            foreach (string propName in propNameSubs)
            {
                if (current == null) { break; } // no need find more

                PropertyInfo property = current.GetType().GetProperty(propName);
                current = property?.GetValue(current);
            }

            return current;
        }

        #endregion


        #region return filter items soure command
        public event EventHandler GetFilterItemsSoure;

        public static readonly DependencyProperty GetFilterItemsSoureCommandProperty = DependencyProperty.Register(
            "GetFilterItemsSoureCommand",
            typeof(ICommand),
            typeof(Table)
            );

        public ICommand GetFilterItemsSoureCommand
        {
            get { return (ICommand)GetValue(GetFilterItemsSoureCommandProperty); }
            set { SetValue(GetFilterItemsSoureCommandProperty, value); }
        }

        /// <summary>
        /// fire event and command
        /// </summary>
        protected void OnGetFilterItemsSoure()
        {
            GetFilterItemsSoure?.Invoke(this, EventArgs.Empty);

            if (GetFilterItemsSoureCommand != null && GetFilterItemsSoureCommand.CanExecute(_filteredItems))
            {
                GetFilterItemsSoureCommand.Execute(_filteredItems);
            }
        }
        #endregion
    }
}
